
package andre.grids.filesystem.server.configuration;

import andre.grids.db.*;
import andre.grids.filesystem.common.*;
import andre.grids.db.dataclasses.*;
import andre.grids.filesystem.common.exceptions.DirectoryNotEmpty;
import andre.grids.filesystem.common.exceptions.FileExists;
import andre.grids.filesystem.server.filemanagement.RandomFile;
import andre.grids.guids.IpTimeGuidFactory;
import andre.grids.util.PathUtils;
import andre.grids.util.log.*;
import java.util.*;

/**
 *
 * @author andre
 */
public class LogLoader {
    
    public void loadFileCreate(DbLogEntry e) throws FileExists {
        String fileMaster = e.getOwnerHostName();
        String [] dados = e.getEntryData().split(":");
        String fileName = dados[0];
        String dirParent = dados[1];
        DbFile file = loadFile(fileName);
        
        if (file != null)
            throw new FileExists(fileMaster, fileName, e.getEntryValue());
        
        file = new DbFile();
        
        DbPeer [] replicasHost = new DbPeer[dados.length - 2];
        DbFileReplica [] fileReplicas = new DbFileReplica[replicasHost.length];
        
        DbPeer master = loadPeer(fileMaster,true,false, true);
        
        DbDirectory dir = loadDirectory(PathUtils.extractParentPath(fileName));
        
        for(int x = 0; x < replicasHost.length; x++) {
            replicasHost[x] = loadPeer(dados[x + 2],true,true,false);
            DbFileReplica replica;
            replica = new DbFileReplica();
            replica.setFile(file);
            replica.setReplicaHost(replicasHost[x]);
            replica.setReplicaLastAccess(new Date(System.currentTimeMillis()));            
        }
        
        Database.getGridFsDb().storeObjectTree(file);
        for(DbFileReplica replica: fileReplicas)
            Database.getGridFsDb().storeObjectTree(replica);
    }
    
    public void loadDirectoryCreate(DbLogEntry e) {
        DbDirectory dir = loadDirectory(e.getEntryValue(),true,e.getOwnerHostName());
        Database.getGridFsDb().getObjects().activate(dir, 1);
        
        // o diretório já existia e foi modificado
        if (!dir.getDirOwner().getPeerName().equals(e.getOwnerHostName())) {
            dir.setDirOwner(loadPeer(e.getOwnerHostName(), true, false, true)); 
            Database.getGridFsDb().storeObjectTree(dir);
        }
    }
    
    public void loadDirectoryDelete(DbLogEntry e) throws DirectoryNotEmpty {
        DbDirectory dir = loadDirectory(e.getEntryValue());
        
        // se o diretório não existe não é necessário deletar
        if (dir == null)
            return;
        
        // se o diretório existe, precisa verficar se não possui nenhum arquivo associado
        // se possuir deve gerar um erro e um log mais antigo deve ser chamado
        // para apagar os arquivos antigos
        if (!directoryIsEmpty(dir))
            throw new DirectoryNotEmpty(dir.getDirName(), DbLogEntry.DELETE_DIRECTORY);
        
        // diretório vazio => deletar
        Database.getGridFsDb().deleteObject(dir);
        
    }
    
    public void loadFileDelete(DbLogEntry e) {
        DbFile file = loadFile(e.getEntryValue());
        
        // se o arquivo não existe, não é necessário deletar
        if (file == null)
            return;
        
        Database.getGridFsDb().deleteObject(file);
        
        if (ConfigurationManager.isFsServer()) {
            RandomFile rfile = new RandomFile((new IpTimeGuidFactory()).generateGuid(file.getFileGuid()));
            if (rfile.getFile().exists())
                rfile.delete();
        }
    }
    
    public void loadFileUpdate(DbLogEntry e) {
        DbFile file = loadFile(e.getEntryValue());        
        if (ConfigurationManager.isFsServer()) {
            RandomFile rfile = new RandomFile((new IpTimeGuidFactory()).generateGuid(file.getFileGuid()));
            if (rfile.getFile().exists()) {
                rfile.open();
                rfile.write(e.getRawData(), 0, e.getRawData().length, Long.parseLong(e.getEntryData()));
            }            
        }
        else {
            Log.logNormalOperation(Log.OP_TYPE_SYNC, "Ignorando entrada: " + e + " pois não é um servidor de arquivos", this);
        }
    }    
    
    public void loadAllLogs(DbLogEntry[] entries) throws DirectoryNotEmpty, IllegalArgumentException, FileExists {
        
        // ajustando o array para organizar por ordem crescente
        // ----------------------------------------------------------------------------------------
        Arrays.sort(entries, new Comparator<DbLogEntry>() {

            public int compare(DbLogEntry o1, DbLogEntry o2) {
                return o1.getEntryDate().compareTo(o2.getEntryDate());
            }
            
        });
        
        for(DbLogEntry l: entries) {
            dispachEntry(l);
            Log.logNormalOperation(Log.OP_TYPE_SYNC, l.toString(), this);
        }
        
        // libera os recursos utilizados para fazer a sincronização dos dados
        System.gc();
    }
    
    /**
     * Chama o procedimento adequado para tratar a entrada no log
     * @param e A entrada que será tratada
     */
    public void dispachEntry(DbLogEntry e) throws IllegalArgumentException, DirectoryNotEmpty, FileExists {
        if (e == null)
            throw new IllegalArgumentException("A entrada não pode ser null");
        
        if (e.getEntryType().equals(DbLogEntry.CREATE_DIRECTORY))
            loadDirectoryCreate(e);
        else if (e.getEntryType().equals(DbLogEntry.CREATE_FILE))
            loadFileCreate(e);
        else if (e.getEntryType().equals(DbLogEntry.DELETE_FILE))
            loadFileDelete(e);
        else if (e.getEntryType().equals(DbLogEntry.DELETE_DIRECTORY))
            loadDirectoryDelete(e);
        else if (e.getEntryType().equals(DbLogEntry.UPDATE_FILE))
            loadFileUpdate(e);
        else
            throw new IllegalArgumentException("A entrada informada é inválida: " + e.toString());
    }
    
    /* Implementações de classe */
    private static final DbPeer loadPeer(String peerName, boolean create, boolean fileServer, boolean dmServer) {
        
        DbPeer peer = (new DbPeerQueries()).getPeerByName(peerName);
        
        // se o host não existe no banco, e deseja criar, então gera o registro
        if (peer == null && create) {
            peer = new DbPeer();
            peer.setPeerName(peerName);
            peer.setDirectoryMaster(dmServer);
            peer.setFileReplica(fileServer);
            Database.getGridFsDb().storeObjectTree(peer);
            return loadPeer(peerName);
        }
        
        return peer;
    }
    /**
     * Busca um host no banco
     * 
     * @param peerName Nome do host
     * @return o Host
     */
    private static final DbPeer loadPeer(String peerName) {
        return loadPeer(peerName,false,false,false);
    }
    
    /**
     * Carrega um diretório no banco, se não existir pode criar (determinado pelo flag create)
     * @param dirName Nome do diretório
     * @param create Flag para criação do novo diretório
     * @param ownerName Nome do host que controla o diretório
     * @return
     */
    private static final DbDirectory loadDirectory(String dirName, boolean create, String ownerName) {
        DbDirectory dir = (new DbDirectoryQueries()).getDirByName(dirName);
        
        // diretório não é reconhecido e deseja criar
        if (dir == null && create) {
            dir = new DbDirectory();
            dir.setDirName(dirName);
            dir.setDirOwner(loadPeer(ownerName));
            Database.getGridFsDb().storeObjectTree(dir);
            return loadDirectory(dirName);
        }
        
        return dir;
    }
    
    /**
     * Busca um diretório no banco
     * @param dirName Nome do diretório
     * @return O diretório
     */
    private static final DbDirectory loadDirectory(String dirName) {
        return loadDirectory(dirName,false,null);
    }
    
    /**
     * Busca um arquivo no banco, se não existe por criar (determinado pelo flag create)
     * @param fileName O nome do arquivo
     * @param create Flag para criar o novo arquivo (se não existir)
     * @param guid Guid do novo arquivo
     * @param parent Nome do diretório que contém o arquivo
     * @return O Arquivo
     */
    private static final DbFile loadFile(String fileName, boolean create, String guid, String parent) {
        DbFile file = (new DbFileQueries()).getByName(parent);
        
        // o arquivo não existe e deseja criar
        if (file == null && create) {
            file = new DbFile();
            file.setFileName(fileName);
            file.setFileGuid(guid);
            file.setParentDir(loadDirectory(parent));
            Database.getGridFsDb().storeObjectTree(file);
            return loadFile(fileName);
        }
        
        return file;
    }
    
    /**
     * Busca um arquivo no banco
     * @param fileName Nome do arquivo
     * @return O Arquivo
     */
    private static final DbFile loadFile(String fileName) {
        return loadFile(fileName, false, null, null);
    }
    
    /**
     * Informa se o diretório está vazio
     * @param parent O Diretório desejado
     * @return True (diretório vazio) / False (não está vazio)
     */
    private static final boolean directoryIsEmpty(DbDirectory parent) {
        DbFile [] files = (new DbFileQueries()).getByParent(parent.getDirName());
        return files == null || files.length == 0;
    }
}
